<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <!-- <script src="./vue.global.js"></script> -->
  <!-- <script src="./template.js"></script> -->
</head>
<body>
  <div id="app">
  </div>
  <div style="height: 100px;" id="kkk" ></div>
  <script>
    let kkk=document.getElementById('kkk')
    let app = document.getElementById('app')
    const MyComponent = {
      setup(props, context) {
        // 如果没有返回函数 会用模板template编译成 虚拟DOM 把返回值放到到含函数的返回对象上
        return () => {
          return {
            tag: 'div',
            props: { style: { color: "pink" } },
            children: '我是组件形式'+state.comp+'我是数组'+state2[0]
          }
        }
      }
    }
    const nodeOps = {
      //插入
      /* 子节点 父节点 参照节点 */
      insert(children, parent, achor) {
        /* 如果有参照物 */
        if (achor) {
          parent.insertBefore(children, achor)
        } else {
          parent.appendChild(children)
        }
      },
      // 移除
      remove(children) {
        const parent = children.parentNode
        parent && parent.removeChild(children)
      },
      createElement(tag) {
        return document.createElement(tag)
      },
      hostSetElement(node, children) { //设置元素文本
        node.textContent = children
      },
      //设置props
      hostSetProps(el, key, value) {
        if (/^on[^a-z]/.test(key)) {
          const eventname = key.slice(2).toLowerCase()
          el.addEventListener(eventname, value)
        }else{
          if (key=='style') {
            for (const key in value) {
              if (value.hasOwnProperty(key)) {
                el.style[key]=value[key]
              }
            }
          }
        }
        
      }
    }
    
    function render(node, container) {
      patch(null, node, container)
    }
    function patch(n1, n2, container) {
      /* 如果是组件的话可能是一个对象 但是此刻暂时不考虑组件的情况 */
      if (typeof n2.tag == 'string') {
        mounteElement(n2, container)
      } else if (typeof n2.tag == 'object') {
        //当tag是一个对象的时候就执行组件挂载
        mountComponent(n2, container)
      }
    }
    function mountComponent(vnode, container) {
      /* 此时我们还可以拿到props，children，slot等属性 */
      const instance = {  //创建一个组件实例
        vnode,
        render: null,//reder方法返回一个组件实例
        tree: null,//此处是一个虚拟DOM树
      }
      const component = vnode.tag
      instance.render = component.setup(vnode.props, instance)
      //setUp返回的是一个函数 
      effect(()=>{ //当内容是个组件的时候 就需要组件单独调用effect 组件的数据 单独收集
        //如果有setUp函数就执行函数 这里可以做Vue2的兼容 可以将之前的opations做兼容 没有setUp函数就执行Vue2的方式并
        instance.tree = instance.render && instance.render()
        patch(null, instance.tree, container)
      })
    }
    function mounteElement(vnode, container) {
      const {
        tag,
        children,
        props
      } = vnode
      /* 生成一个节点 节点和vnode之间有映射关系 */
      const el = (vnode.el = nodeOps.createElement(tag))
      // console.log('el', vnode,el)
      if (props) {
        for (const key in props) {
          nodeOps.hostSetProps(el, key, props[key])
        }
      }
      // 判断是否有子节点 
      // 如果有子节点
      if (Array.isArray(children)) {
        mountChildren(children, el)
      } else {
        if (typeof children !== 'object') {
          nodeOps.hostSetElement(el, children)
        } else {

        }
      }
      nodeOps.insert(el, container)
    }
    function mountChildren(children, el) {
      for (let i = 0; i < children.length; i++) {
        let child = children[i]
        patch(null, child, el)
        /* 
          当我们遇到一个节点的时候就再次重复操作 首先判断有没有tag或者是一个组件对象。。。等等
          是则调用节点挂载方法 首先使用runtime-dom 返回一个元素 检查是否有子节点如有就遍历子节点
        */
      }
    }
    // 响应式
    let activeEffect;
    function effect(fn) {
      activeEffect = fn //数据变化之后再次执行render
      fn()
      activeEffect=null  //如果此时不讲activeEffect清空的话 再下方检测
    }
    // 包装返回响应式数据
    function reactive(target) {
      // 将原来的属性整个用Proxy包装 取到的值如果是对象的话
      // Proxy不用重写每个属性 性能高
      return new Proxy(target, {
        set(target, key, value, receiver) { //拦截器
          // 静态方法 Reflect.set() 工作方式就像在一个对象上设置一个属性。并返回设置属性是否成功boolean
          const res = Reflect.set(target, key, value, receiver)  // 当set的时候使用React.set将值设置到目标值上
          console.log('设置值', target[key], res, target);
          trigger(target,key)  //触发依赖收集
          //res && activeEffect() //当设置成功就重新执行render 
          //当对象里的数据我们并没有用到，更改时却触发更新是不应该的，此时我们需要用到依赖收集，
          //即只有当是我们正在使用的数据变化的时候，才触发更新
          return res
        },
        // 当我们需要取到的值是对象深层次数据的时候，会再次返回一个代理之后的数据   --懒递归
        get(target, key, receiver) {
          if (typeof target[key] == 'object') {
            return reactive(target[key])
          }else{
            track(target,key) //依赖收集 在用到当前对象的时候将使用到值记录下来
            let res=Reflect.get(target,key,receiver)
            return res
          }
        }
      })
    }
    // 一个state对象的部分键可能会被一个或者多个effect去使用
    // 即当前对象对应的所有被使用的键值都应该被记录 此时需要用到WeakMap数据结构 普通对象的键不能是对象 但是WeakMap可以，且
    // WeakMap的键同样是不能重复的
    // 为什么不使用map，当键为对象的时候且内容过多，会产生内存泄漏，且不会触发V8引擎垃圾回收
    // 依赖收集 
    // 
    const tragetMap=new WeakMap()
    function track(target,key){
      console.log('依赖收集',target,key);
      let depsMap=tragetMap.get(target)//在当前weakMap上拿到当前使用对象是否存在
      if (!depsMap) {
        // 如果当前对象没有在依赖里，即之前没有使用，就新建一个map将，将这个将要使用的对象加入到依赖项里去
        tragetMap.set(target,(depsMap=new Map()))
      }
      let res=depsMap.get(key) //收集这个所依赖对象用到的数据项
      if (!res){
        depsMap.set(key,(res=new Set()))
        //为什么一个effect这里会用set数据结构 因为一个effect对应的可能不止一个activeEffect 需要直接
        //去重复
      }
      // 有渲染函数且当前键没有对应渲染函数
      // 如果activeEffect 上方没有清空 当改变name的时候会重新 get num 但是此时activeEffect等于组件更新 但是用set.has()检测为fasle 取反为true 前面也为真  就会将组件更新 重新加入到num上来 导致重新更新两遍同样的事情发生在name和comp上
      //且set.has()无法分辨箭头函数是否已存在 这个时候就可以用id去标识一个render函数使得其不重复 
      // activeEffect只有在执行effect的fn函数的时候才会是有值的，也就是在解析虚拟dom的时候
      console.log(res);
      // let comp={render:MyComponent}
      if (activeEffect && !res.has(activeEffect) ) {
        // 第一遍完成之后 每个key都对应着一个渲染函数 当改变name的以时候要再次读取 num 会再次走入 get  
        res.add(activeEffect)
      }
    }
    //依赖检测 如果当前设置改变的值在依赖收集里 = 》 允许触发render更新
    function trigger(target,key){
      // 如果当前对象没有在weakmap上取值 
      const resMap=tragetMap.get(target)
      console.log('点击num+13',resMap,target,key,tragetMap);
      if (!resMap) {return}
      const res=resMap.get(key) //数据需要更改的键值
      res && res.forEach(item => {
          console.log('resMap',resMap,key);   
          item()
      });
    }
    //先执行reactive将数据源代理
    const state = reactive({
      num: {xx:1},
      name:'xj',
      comp:1212,
      fgfgfggf:676767
    })
    const state2=reactive([3,2])
    effect(() => {
      const vnode = {
        tag: 'div',
        props: {
          style:{'color':'red'}
        },
        children: [
        {
          tag: 'p',
          props: {
            fontSize: '28px'
          },
          children: 'Vue3虚拟DOM转化' + state.num.xx
        },{
          tag: 'button',
          props: {
            color: 'blue',
            onClick: () => state.num.xx += 13
          },
          children: 'num+13'
        },{
          tag: 'button',
          props: {
            color: 'blue',
            onClick: () => state2[0]=999999
          },
          children: '改变数组'
        },{
          tag: 'button',
          props: {
            color: 'blue',
            onClick: () => state.comp += 1
          },
          children: '来点我'
        }, {
          tag: 'button',//标签就是当前组件
          props: {
            onClick: () => state.name = 'dddddd'
          },
          children: '来点我2'
        },{
          tag: MyComponent,//标签就是当前组件
          props: null,
          children: ''
        },{
          tag:'p',
          children:state.name
        }]
      }
      render(vnode, app)
    })
    // 第一遍完成的时候num和nane对应的set值到底有几个 是什么   什么时候变成两个的  加入的是什么
    render({
      tag: 'div',
      props: {
        style:{ color: 'red'}
      },
      children:"hwp"
    },app)

    setTimeout(() => {
      render({
      tag: 'div',
      props: {
        color: 'red'
      },
      children:"hwp"
    },app)
    }, 1000);
    /* 组件的setUp函数返回一个函数 函数函数返回一个 */
  </script>
</body>

</html>